using System;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;

namespace Dida
{
	[Flags]
	public enum UnmanagedResMgrFlags
	{
		None = 0
	}

	/// <summary>
	/// A class with static methods for dealing with unmanaged resources.
	/// </summary>
	/// <remarks>
	/// See <see cref = "UnmanagedResource"/> for more details.
	/// </remarks>
	public static class UnmanagedResourceManager
	{
		enum InitedState
		{
			NotInited,
			Inited,
			Freed,
		};

		static InitedState inited = InitedState.NotInited;
		static Thread mainThread;

		static HashSet<UnmanagedResource.HashableWeakRef> uncategorized = 
			new HashSet<UnmanagedResource.HashableWeakRef>();
		static HashSet<UnmanagedResourceCategory> categories = 
			new HashSet<UnmanagedResourceCategory>();
		static ThreadedTaskQueue deletionsTaskQueue = new ThreadedTaskQueue();

		/// <summary>
		/// Should be called before you create any unmanaged resource.
		/// </summary>
		/// <param name="flags">A combination of <see cref = "UnmanagedResMgrFlags"/> flags.</param>
		/// <seealso cref = "Free"/>
		/// <remarks>
		/// The thread where you call this method from will be considered the main thread.
		/// </remarks>
		public static void Init(UnmanagedResMgrFlags flags)
		{
			Debug.Assert(inited != InitedState.Inited, "You shouldn't call UnmanagedResourceManager.Init more than once");

			mainThread = Thread.CurrentThread;
			inited = InitedState.Inited;
		}

		/// <summary>
		/// Disposes all unmanaged resources that exist, even if there are still
		/// references to that object.
		/// </summary>
		/// <seealso cref="Init"/>
		public static void Free()
		{
			if (inited != InitedState.Inited)
				return;

			AssertMainThread();
			FreeResources();

			inited = InitedState.Freed;
		}

		public static UnmanagedResourceCategory CreateCategory()
		{
			UnmanagedResourceCategory cat = new UnmanagedResourceCategory();
			categories.Add(cat);
			return cat;
		}

		/// <summary>
		/// Frees all unmanaged resources of the given category.
		/// </summary>
		/// <param name="cat">The category whos objects have to be freed.</param>
		public static void FreeCategory(UnmanagedResourceCategory cat)
		{
			if(inited != InitedState.Inited)
				return;
			
			Debug.Assert(categories.Contains(cat));

			cat.FreeAll(false);
			categories.Remove(cat);
		}

		/// <summary>
		/// Frees all unmanaged resources that have been garbage collected.
		/// </summary>
		/// <remarks>
		/// <para>
		/// When an unmanaged resource wrapper (of a class derived from <see cref = "UnmanagedResource"/>)
		/// gets finalized, it won't be deleted immediately, but instead it will be put 
		/// in a queue, and all objects in that queue will be deleted when this method gets 
		/// called.
		/// </para>
		/// <para>
		/// Because unmanaged objects only get freed when this method gets called, 
		/// it's important that it gets called quite often. Usually, it's just called 
		/// from the game's main loop.
		/// </para>
		/// <para>
		/// This method must be called from the main thread.
		/// </para>
		/// </remarks>
		public static void Update()
		{
			FlushDeleteQueue();
		}

		private static void FreeResources()
		{
			AssertMainThread();

			foreach (UnmanagedResource.HashableWeakRef resRef in uncategorized)
			{
				UnmanagedResource resource = (UnmanagedResource)resRef.Target;
				if (resource != null)
					resource.Delete(false);
			}
			uncategorized.Clear();

			foreach (UnmanagedResourceCategory cat in categories)
				cat.FreeAll(false);
			categories.Clear();

			FlushDeleteQueue();
		}

		private static void FlushDeleteQueue()
		{
			Debug.Assert(inited == InitedState.Inited, "The UnamangedResourceManager class hasn't been initialized (you have to call it's Init static method");
			AssertMainThread();

			deletionsTaskQueue.Flush();
		}

		internal static void RemoveResourceFromList(UnmanagedResource res)
		{
			uncategorized.Remove(new UnmanagedResource.HashableWeakRef(res));
		}

		[Conditional("DEBUG")]
		internal static void AssertMainThread()
		{
			Debug.Assert(inited != InitedState.NotInited,"The UnamangedResourceManager class hasn't been initialized (you have to call it's Init static method");
			Debug.Assert(Thread.CurrentThread == mainThread, "This should only be called from the main thread");
		}

		internal static bool AreWeInMainThread
		{
			get
			{
				Debug.Assert(inited != InitedState.NotInited, "The UnamangedResourceManager class hasn't been initialized (you have to call it's Init static method");
				return Thread.CurrentThread == mainThread;
			}
		}

		internal static void RegisterResource(UnmanagedResource res)
		{
			Debug.Assert(inited == InitedState.Inited, "The UnamangedResourceManager class hasn't been initialized (you have to call it's Init static method");
			lock (uncategorized)
			{
				uncategorized.Add(new UnmanagedResource.HashableWeakRef(res));
			}
		}

		internal static void RegisterDeletionTask(Task deleteTask)
		{
			Debug.Assert(inited != InitedState.NotInited, "The UnamangedResourceManager class hasn't been initialized (you have to call it's Init static method");
			deletionsTaskQueue.Add(deleteTask);
		}
	}

	public class UnmanagedResourceCategory
	{
		HashSet<UnmanagedResource.HashableWeakRef> resources =
			new HashSet<UnmanagedResource.HashableWeakRef>();

		internal UnmanagedResourceCategory() { }

		internal void RegisterResource(UnmanagedResource resource)
		{
			lock (resources)
			{
				resources.Add(new UnmanagedResource.HashableWeakRef(resource));
			}
		}

		internal void FreeAll(bool removeFromList)
		{
			foreach (UnmanagedResource.HashableWeakRef resRef in resources)
			{
				UnmanagedResource resource = (UnmanagedResource)resRef.Target;
				if (resource != null)
					resource.Delete(removeFromList);
			}
			resources.Clear();
		}

		internal void RemoveResourceFromList(UnmanagedResource res)
		{
			resources.Remove(new UnmanagedResource.HashableWeakRef(res));
		}
	}

	public abstract class UnmanagedResource : IDisposable
	{
		bool disposed = false;

		UnmanagedResourceCategory category;

		int globalIndex;
		static int nextGlobalIndex = 0;


		public UnmanagedResource()
		{
			UnmanagedResourceManager.AssertMainThread();
			UnmanagedResourceManager.RegisterResource(this);

			category = null;

			globalIndex = Interlocked.Increment(ref nextGlobalIndex);
		}

		public UnmanagedResource(UnmanagedResourceCategory category)
			: this()
		{
			this.category = category;
		}

		~UnmanagedResource()
		{
			Delete(true);
		}

		protected virtual void Dispose(bool disposing)
		{
			Delete(true);
		}

		internal void Delete(bool removeFromList)
		{
			if (disposed)
				return;
			GC.SuppressFinalize(this);

			Task deleteHandler = delegate()
			{
				if (!disposed)
				{
					FreeUnmanagedResources();
					
					if (removeFromList)
					{
						if (category != null)
							category.RemoveResourceFromList(this);
						else
							UnmanagedResourceManager.RemoveResourceFromList(this);
					}

					disposed = true;
				}
			};

			if (UnmanagedResourceManager.AreWeInMainThread)
			{
				deleteHandler();
			}
			else
			{
				UnmanagedResourceManager.RegisterDeletionTask(deleteHandler);
			}
		}

		/// <summary>
		/// Should free the actual unmanaged resources.
		/// </summary>
		/// <remarks>
		/// This method should be implemented by derived classes, and is where the
		/// unmanaged resource can actually be freed. This method is guaranteed to
		/// be called in the main thread, (ie. The thread from where you call
		/// <see cref = "UnmanagedResourceManager.Update"/> and <see cref = "UnmanagedResourceManager.Free"/>).
		/// </remarks>
		protected abstract void FreeUnmanagedResources();

		/// <summary>
		/// Call this to dispose the resource.
		/// </summary>
		/// <remarks>
		/// If this method is called from the main thread, the resource will be
		/// freed immediately, if it's called from a different thread, it's freed
		/// the next time the <see cref = "UnmanagedResourceManager.Update"/> 
		/// method is called.
		/// </remarks>
		public void Dispose()
		{
			if (disposed)
				return;
			Dispose(true);
		}

		internal int GlobalIndex
		{
			get { return globalIndex; }
		}

		internal class HashableWeakRef
		{
			WeakReference weakRef;
			int index;

			public HashableWeakRef(UnmanagedResource o)
			{
				weakRef = new WeakReference(o);
				index = o.GlobalIndex;
			}

			public override int GetHashCode()
			{
				return index;
			}

			public override bool Equals(object obj)
			{
				return index == ((HashableWeakRef)obj).index;
			}

			public UnmanagedResource Target
			{
				get
				{
					return (UnmanagedResource)weakRef.Target;
				}
			}
		}
	}
}